package com.softao.ming.gde.internal.core.launcher.tasks;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;

import com.softao.ming.gde.core.BundleRegistry;
import com.softao.ming.gde.core.launcher.LauncherContext;
import com.softao.ming.gde.core.launcher.LauncherSettings;
import com.softao.ming.gde.core.model.IBundleGwtPluginFile;
import com.softao.ming.gde.core.model.IBundleModel;
import com.softao.ming.gde.core.model.IBundlePluginFile;
import com.softao.ming.gde.core.model.IConfigurationElement;
import com.softao.ming.gde.core.model.IExtension;
import com.softao.ming.gde.internal.core.GdeCore;

/**
 * 
 */
public class BootstrapApplicationTask extends AbstractLauncherTask {
	/** */
	private static final String APP_EXT_POINT_ID = "com.softao.ming.gwt.core.runtime.applications";
	/** */
	private static final String PRODUCT_EXT_POINT_ID = "com.softao.ming.gwt.core.runtime.products";

	@Override
	public void perform(LauncherContext context, IProgressMonitor monitor)
			throws CoreException {
		monitor.subTask("Creating bootstrap application...");
		
		SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, 3);
		try {
			try {
				generate(context, subMonitor);
			} catch (IOException e) {
				String msg = "Create bootstrap failed.";
				IStatus status = new Status(IStatus.ERROR, GdeCore.PLUGIN_ID, msg, e);
				throw new CoreException(status);
			}
		} finally {
			subMonitor.done();
		}
	}
	
	/**
	 * Generates the bootstrap application to start the GWT product.
	 * @param context 
	 * @param monitor 
	 * 
	 * @return <code>true</code> if the bootstrap application generated
	 *         successfully; otherwise, <code>false</code>.
	 * @throws IOException 
	 */
	public boolean generate(LauncherContext context, IProgressMonitor monitor)
			throws IOException {
		monitor.subTask("Creating public resources...");
		if (!createPublic(context)) {
			return false;
		}
		monitor.worked(1);

		monitor.subTask("Creating module file...");
		if (!createBootstrapModule(context)) {
			return false;
		}
		monitor.worked(1);

		monitor.subTask("Creating entry point class...");
		if (!createEntryPoint(context)) {
			return false;
		}
		monitor.worked(1);

		return true;
	}

	private boolean createPublic(LauncherContext context) {
		LauncherSettings settings = context.getSettings();
		
		File publicDir = new File(settings.getOutputLocation() + "/war");
		publicDir.mkdirs();
		File defaultHtml = new File(publicDir.toString() + "/default.html");
		
		StringBuilder sb = new StringBuilder();
		generateDocType(context, sb);
		sb.append("<html>\n");
		sb.append("    <head>\n");
		sb.append("        <title>" + getTopElement(context).getAttribute("name") + "</title>\n");
		sb.append("        <script language='javascript' src='bootstrap/bootstrap.nocache.js'></script>\n");	
		sb.append("    </head>\n");
		sb.append("    <body>\n");
		sb.append("        <iframe src=\"javascript:''\" id=\"__gwt_historyFrame\" style=\"width:0;height:0;border:0\"></iframe>\n");
		sb.append("    </body>\n");
		sb.append("</html>");
		
		try {
			writeFile(defaultHtml, sb);
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	private void generateDocType(LauncherContext context, StringBuilder sb) {
		BundleRegistry bundleRegistry = context.getBundleRegistry();
		IBundleModel startBundle = null;
		for (IBundleModel bundle : bundleRegistry.allSourceBundles()) {
			IBundlePluginFile pluginFile = bundle.getPluginFile();
			if (pluginFile == null) {
				continue;
			}
			
			for (IExtension extension : pluginFile.getExtensions("PRODUCT_EXT_POINT_ID")) {
				if (extension.getUniqueIdentifier().equals(context.getSettings().getTargetElementId())) {
					startBundle = bundle;
					break;
				}
			}
		}
		
		if (startBundle == null) {
			return;
		}
		String htmlVersion = startBundle.getGwtPluginFile().getHtmlVersion();
		
		if (IBundleGwtPluginFile.HTML_401_STRICT.equals(htmlVersion)) {
			sb.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n");
		} else if (IBundleGwtPluginFile.HTML_401_TRANSITIONAL.equals(htmlVersion)) {
			sb.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
		} else if (IBundleGwtPluginFile.HTML_401_FRAMESET.equals(htmlVersion)) {
			sb.append("<!DOCTYPE HTML PUBLIC \"-//W3C//-//W3C//DTD HTML 4.01 Frameset//EN\" \"http://www.w3.org/TR/html4/frameset.dtd\">\n");
		} else if (IBundleGwtPluginFile.HTML_DEFAULT_VERSION.equals(htmlVersion)) {
			// Default is nothing to do.
		} else {
			throw new RuntimeException("Not recognized HTML version " + htmlVersion);
		}
	}

	private boolean createBootstrapModule(LauncherContext context) throws IOException {
		LauncherSettings settings = context.getSettings();
		
		File publicDir = new File(settings.getOutputLocation() + "/src/com/softao/ming/gwt/bootstrap");
		publicDir.mkdirs();
		File moduleFile = new File(publicDir.toString() + "/MingBootstrap.gwt.xml");
		
		StringBuilder sb = new StringBuilder();
		sb.append("<module rename-to=\"bootstrap\">\n");
		sb.append("    <!-- Inherit GWT Standard Module -->\n");
		sb.append("    <inherits name='com.google.gwt.user.User'/>\n");
		sb.append("\n");
		sb.append("    <!-- Specify the application entry point class -->\n");
		sb.append("    <entry-point class='com.softao.ming.gwt.bootstrap.client.BootstrapApplication'/>\n");
		sb.append("\n");
		sb.append("    <!-- Processing plugin module -->\n");
		
		// For each plugin, search for the module, and inherits all of them.
		for (IBundleModel bundle : context.getBundleRegistry().allSourceBundles()) {
			printModule(sb, bundle);
		}
		
		sb.append("</module>");
		
		try {
			writeFile(moduleFile, sb);
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	private boolean createEntryPoint(LauncherContext context) {
		LauncherSettings settings = context.getSettings();
		
		IConfigurationElement prodElement = getTopElement(context);
		IConfigurationElement appElement = getApplicationElement(context, prodElement.getAttribute("application"));
		
		File publicDir = new File(settings.getOutputLocation() + "/src/com/softao/ming/gwt/bootstrap/client");
		publicDir.mkdirs();
		File entryPointFile = new File(publicDir.toString() + "/BootstrapApplication.java");
		
		StringBuilder sb = new StringBuilder();
		sb.append("package com.softao.ming.gwt.bootstrap.client;\n");
		sb.append("\n");
		sb.append("import com.google.gwt.core.client.EntryPoint;\n");
		sb.append("import com.softao.ming.gwt.core.internal.runtime.client.ExtensionRegistry;\n");
		sb.append("import com.softao.ming.gwt.core.internal.runtime.client.ObjectFactory;\n");
		sb.append("import com.softao.ming.gwt.core.runtime.client.application.IObjectFactory;\n");
		sb.append("import com.softao.ming.gwt.core.runtime.client.application.Platform;\n");
		sb.append("\n");
		sb.append("public class BootstrapApplication implements EntryPoint {\n");
		sb.append("\n");
		sb.append("    public void onModuleLoad() {\n");
		sb.append("        registerPlugin();\n");
		sb.append("        registerObjectFactory();\n");
		sb.append("        " + appElement.getAttribute("class") + " application = new " + appElement.getAttribute("class") + "();\n");
		sb.append("        application.start();\n");
		sb.append("    }\n");
		sb.append("\n");
		sb.append("    private void registerPlugin() {\n");
		sb.append("        ExtensionRegistry registry = (ExtensionRegistry)Platform.getExtensionRegistry();\n");
		
		for (IBundleModel bundle : context.getBundleRegistry().allSourceBundles()) {
			if (bundle.getPluginFile() == null) {
				continue;
			}
			String content = bundle.getPluginFile().getContent();
			registerPluginContent(sb, content);
		}
		
		sb.append("    }\n");
		sb.append("\n");
		
		List<String> pluginClasses = new ArrayList<String>();
		for (IBundleModel bundle : context.getBundleRegistry().allSourceBundles()) {
			IBundlePluginFile pluginFile = bundle.getPluginFile();
			if (pluginFile == null) {
				continue;
			}
			
			for (String extensionPointId : pluginFile.getExtensionPoints()) {
				for (IExtension extension : pluginFile.getExtensions(extensionPointId)) {
					for (IConfigurationElement element : extension.getConfigurationElements()) {
						if (element.hasAttribute("class")) {
							pluginClasses.add(element.getAttribute("class"));
						}
					}
				}
			}
		}
		
		sb.append("    private void registerObjectFactory() {\n");
		sb.append("        ObjectFactory objectFactory = (ObjectFactory)Platform.getObjectFactory();\n");
		sb.append("        objectFactory.merge(new IObjectFactory() {\n");
		sb.append("            public <T> T create(Class<T> type, String className) {\n");
		for (String type : pluginClasses) {
			sb.append("                if (\"" + type + "\".equals(className)) \n");
			sb.append("                    return (T)new " + type + "();\n");
		}
		sb.append("                return null;\n");
		sb.append("            }\n");
		sb.append("\n");
		sb.append("            public String[] getClassNames() {\n");
		sb.append("                return new String[] {\n");
		for (String type : pluginClasses) {
			sb.append("                \"" + type + "\",\n");
		}
		sb.append("                };\n");
		sb.append("            }\n");
		sb.append("        });\n");
		sb.append("    }\n");
		sb.append("\n");
		
		sb.append("}");
		
		try {
			writeFile(entryPointFile, sb);
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	private void registerPluginContent(StringBuilder sb, String pluginXml) {
		pluginXml = pluginXml.replace("\"", "\\\"").replace("\n", "\"  +\n\"");
		sb.append("        registry.registerPlugin(\"" + pluginXml + "\");\n");
	}

	private void printModule(StringBuilder sb, IBundleModel bundle) throws IOException {
		if (bundle.getContainerFile().isFile()) {
			printModulesInJar(sb, new ZipFile(bundle.getContainerFile()));
		} else {
			String[] sourceFolders = bundle.getBuildFile() == null ? 
					new String[] {"src"} : bundle.getBuildFile().getSourceFolders();
			for (String sourceFolder : sourceFolders) {
				File sourceFile = new File(bundle.getContainerFile(), sourceFolder);
				printModulesInFolder(sb, sourceFile, "");
			}
		}
	}

	private void printModulesInJar(StringBuilder sb, ZipFile zipFile) {
		Enumeration<? extends ZipEntry> entries = zipFile.entries();
		while (entries.hasMoreElements()) {
			ZipEntry zipEntry = entries.nextElement();
			if (zipEntry.isDirectory()) {
				continue;
			}
			
			String entryName = zipEntry.getName();
			if (entryName.endsWith(".gwt.xml")) {
				entryName = entryName.substring(0, entryName.length() - ".gwt.xml".length());
				entryName = entryName.replace('/', '.').replace('\\', '.');
				sb.append("    <inherits name='" + entryName + "'/>\n");
			}
		}
	}
	private static void printModulesInFolder(StringBuilder sb, File parent, String path) {
		for (File file : parent.listFiles()) {
			if (file.isDirectory()) {
				if (path.length() == 0) {
					printModulesInFolder(sb, file, file.getName());
				} else {
					printModulesInFolder(sb, file, path + "." + file.getName());
				}
			} else {
				String fileName = file.getName();
				if (fileName.endsWith(".gwt.xml")) {
					String moduleName = path.length() == 0 ? fileName : path + "." + fileName;
					moduleName = moduleName.substring(0, moduleName.length() - ".gwt.xml".length());
					sb.append("    <inherits name='" + moduleName + "'/>\n");
				}
			}
		}
	}
	
	/**
	 * @param buildEntryStream
	 * @return source
	 */
	public static String[] getSourceFolders(InputStream buildEntryStream) {
		Properties prop = new Properties();
		try {
			prop.load(buildEntryStream);
		} catch (IOException e) {
			e.printStackTrace();
			return new String[0];
		}
		String source = prop.getProperty("source..");
		return source == null ? new String[0] : source.split(",");
	}
		
	private static void writeFile(File file, StringBuilder sb) throws IOException {
		FileOutputStream fos = new FileOutputStream(file);
		OutputStreamWriter osw = new OutputStreamWriter(fos);
		BufferedWriter writer = new BufferedWriter(osw);
		writer.append(sb.toString());
		writer.flush();
		fos.close();
	}

	private IConfigurationElement getApplicationElement(LauncherContext context, String applicationId) {
		for (IBundleModel bundle : context.getBundleRegistry().allSourceBundles()) {
			IBundlePluginFile pluginFile = bundle.getPluginFile();
			if (pluginFile == null) {
				continue;
			}
			
			for (IExtension extension : pluginFile.getExtensions(APP_EXT_POINT_ID)) {
				for (IConfigurationElement element : extension.getConfigurationElements()) {
					String id = element.getAttribute("id");
					if (applicationId.equals(id)) {
						return element;
					}
				}
			}
		}
		return null;
	}
	
	private IConfigurationElement getTopElement(LauncherContext context) {
		for (IBundleModel bundle : context.getBundleRegistry().allSourceBundles()) {
			IBundlePluginFile pluginFile = bundle.getPluginFile();
			if (pluginFile == null) {
				continue;
			}
			
			for (IExtension extension : pluginFile.getExtensions(PRODUCT_EXT_POINT_ID)) {
				for (IConfigurationElement element : extension.getConfigurationElements()) {
					String id = element.getAttribute("id");
					if (context.getSettings().getTargetElementId().equals(id)) {
						return element;
					}
				}
			}
		}
		return null;
	}
}
